package com.rj.core.algorithm;

import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.spec.SM2ParameterSpec;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.security.*;

/**
 * SM2算法
 */
public enum SM2Algorithm {
    /**
     * 默认曲线
     */
    SM2("sm2p256v1");


    private ECNamedCurveParameterSpec curveParameterSpec;

    SM2Algorithm(String curveParameterSpecName) {
        this.curveParameterSpec = ECNamedCurveTable.getParameterSpec(curveParameterSpecName);
    }

    /**
     * 获取签名实例
     *
     * @param userId
     * @return
     * @throws Exception
     */
    public Signature getSignatureInstance(byte[] userId) throws Exception {
        Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
        if (userId != null) {
            signature.setParameter(new SM2ParameterSpec(userId));
        }
        return signature;
    }

    /**
     * SM2加密解密 核心函数
     *
     * @param data 数据
     * @param key  密钥
     * @param mode 加密解密模式
     * @return
     * @throws InvalidCipherTextException
     */
    protected byte[] crypt(byte[] data, Key key, SM2Engine.Mode mode) {
        boolean isPublicKey = (key instanceof PublicKey);
        try {
            CipherParameters cipherParameters;
            if (isPublicKey) {
                BCECPublicKey bcPublicKey = (BCECPublicKey) key;
                ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(bcPublicKey.getQ(),
                        new ECDomainParameters(curveParameterSpec.getCurve(), curveParameterSpec.getG(), curveParameterSpec.getN()));
                cipherParameters = new ParametersWithRandom(publicKeyParameters, new SecureRandom());
            } else {
                BCECPrivateKey bcPrivateKey = (BCECPrivateKey) key;
                cipherParameters = new ECPrivateKeyParameters(bcPrivateKey.getD(),
                        new ECDomainParameters(curveParameterSpec.getCurve(), curveParameterSpec.getG(), curveParameterSpec.getN()));
            }
            SM2Engine engine = new SM2Engine(mode);
            engine.init(isPublicKey, cipherParameters);
            return engine.processBlock(data, 0, data.length);
        } catch (InvalidCipherTextException e) {
            throw new RuntimeException(isPublicKey ? "SM2加密解密失败" : "SM2解密失败", e);
        }

    }

    /**
     * 密钥对生成
     *
     * @return
     */
    public KeyPair generateKeyPair() {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
            keyPairGenerator.initialize(curveParameterSpec, new SecureRandom());
            return keyPairGenerator.generateKeyPair();
        } catch (Exception e) {
            throw new RuntimeException("SM2密钥对生成失败", e);
        }

    }

    /**
     * 获取SM2公钥
     *
     * @param publicKey
     * @param compressed
     * @return 返回16进制字符串
     */
    public byte[] getPublicKey(PublicKey publicKey, boolean compressed) {
        BCECPublicKey bcPublicKey = (BCECPublicKey) publicKey;
        return bcPublicKey.getQ().getEncoded(compressed);
    }

    /**
     * 获取SM2私钥
     *
     * @param privateKey
     * @return 返回16进制字符串
     */
    public byte[] getPrivateKey(PrivateKey privateKey) {
        BCECPrivateKey bcPrivateKey = (BCECPrivateKey) privateKey;
        return bcPrivateKey.getD().toByteArray();
    }

    /**
     * 通过获取公钥
     *
     * @param publicKeyBytes
     * @return
     */
    public BCECPublicKey getPublicKey(byte[] publicKeyBytes) {
        try {

            ECCurve curve = curveParameterSpec.getCurve();
            ECPoint point = curve.decodePoint(publicKeyBytes);
            ECPublicKeySpec keySpec = new ECPublicKeySpec(point, curveParameterSpec);
            KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
            return (BCECPublicKey) keyFactory.generatePublic(keySpec);
        } catch (Exception e) {
            throw new RuntimeException("公钥转换失败", e);
        }
    }

    /**
     * 通过获取私钥
     *
     * @param privateKeyBytes 16进制私钥
     * @return
     */
    public BCECPrivateKey getPrivateKey(byte[] privateKeyBytes) {
        try {

            // 将字节数组转换为BigInteger
            BigInteger privateKeyInt = new BigInteger(1, privateKeyBytes);
            // 创建ECPrivateKeySpec
            ECPrivateKeySpec keySpec = new ECPrivateKeySpec(privateKeyInt, curveParameterSpec);
            KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
            return (BCECPrivateKey) keyFactory.generatePrivate(keySpec);
        } catch (Exception e) {
            throw new RuntimeException("私钥转换失败", e);
        }
    }

    /**
     * 签名
     *
     * @param data
     * @param privateKey
     * @param userId
     * @return
     */
    public byte[] sign(byte[] data, PrivateKey privateKey, byte[] userId) {
        try {
            Signature signature = getSignatureInstance(userId);
            signature.initSign(privateKey);
            signature.update(data);
            return signature.sign();
        } catch (Exception e) {
            throw new RuntimeException("签名失败", e);
        }

    }

    /**
     * 验证签名
     *
     * @param data
     * @param signatureData
     * @param publicKey
     * @param userId
     * @return
     */

    public boolean verify(byte[] data, byte[] signatureData, PublicKey publicKey,
                          byte[] userId) {
        try {
            Signature signature = getSignatureInstance(userId);
            signature.initVerify(publicKey);
            signature.update(data);
            return signature.verify(signatureData);
        } catch (Exception e) {
            throw new RuntimeException("验证签名失败", e);
        }

    }


    /**
     * SM2加密
     *
     * @param data
     * @param publicKey
     * @param mode
     * @return
     * @throws Exception
     */
    public byte[] encrypt(byte[] data, PublicKey publicKey, SM2Engine.Mode mode) throws Exception {
        return crypt(data, publicKey, mode);
    }

    /**
     * SM2解密
     *
     * @param cipherText
     * @param privateKey
     * @param mode
     * @return
     * @throws Exception
     */
    public byte[] decrypt(byte[] cipherText, PrivateKey privateKey, SM2Engine.Mode mode) throws Exception {
        return crypt(cipherText, privateKey, mode);
    }


}